package com.javacoo.junit.generator.codegen;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.VariableDeclarationExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.stmt.ExpressionStmt;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.javacoo.junit.generator.api.ProgressCallback;
import com.javacoo.junit.generator.api.plugin.TemplatePlugin;
import com.javacoo.junit.generator.config.ClassConfiguration;
import com.javacoo.junit.generator.config.Context;
import com.javacoo.junit.generator.internal.MethodHelper;
import com.javacoo.junit.generator.internal.ObjectFactory;
import com.javacoo.junit.generator.internal.data.PrimitiveType;
import com.javacoo.junit.generator.internal.util.JavaparserUtility;
import com.javacoo.junit.generator.internal.util.StrKit;
import com.javacoo.junit.generator.model.MethodMetadata;
import com.javacoo.xkernel.spi.ExtensionLoader;

import static com.javacoo.junit.generator.internal.util.StringUtility.stringHasValue;
import static com.javacoo.junit.generator.internal.util.messages.Messages.getString;

/**
 * 基于Javaparser实现的生成器
 * <p>说明:</p>
 * <li></li>
 *
 * @author duanyong@jccfc.com
 * @date 2021/1/28 22:26
 */
public class JavaparserJunitGenerator extends AbstractGeneratorPlugin {

    private JavaSymbolSolver symbolSolver;
    /**
     * 初始化后处理
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/29 9:08
     * @return: void
     */
    @Override
    protected void initAfter(){
        try {
            this.symbolSolver = JavaparserUtility.initJavaparser(this.contextEnvment.getCallback());
            StaticJavaParser.getConfiguration().setSymbolResolver(this.symbolSolver);
        } catch (IOException ioException) {
            ioException.printStackTrace();
            this.warnings.add(ioException.getMessage());
        }
    }
    /**
     * 执行生成文件
     * <li></li>
     *
     * @param context : 待执行的上下文
     * @author duanyong@jccfc.com
     * @date 2021/1/5 9:58
     * @return: void
     */
    @Override
    protected void execute(Context context) {
        this.contextEnvment.getCallback().startTask(getString("Progress.0"));
        try {
            //模板插件
            TemplatePlugin templatePlugin = ExtensionLoader.getExtensionLoader(TemplatePlugin.class).getDefaultExtension();
            //如果设置了模板处理类
            if(context.getTemplateConfiguration() != null && stringHasValue(context.getTemplateConfiguration().getTemplateHandlerName())){
                //获取指定处理类
                templatePlugin = ExtensionLoader.getExtensionLoader(TemplatePlugin.class).getExtension(context.getTemplateConfiguration().getTemplateHandlerName());
                if(templatePlugin == null){
                    warnings.add(getString("RuntimeError.22",context.getTemplateConfiguration().getTemplateHandlerName()));
                    return;
                }
            }
            for (ClassConfiguration classConfiguration : context.getClassConfigurations()) {
                this.contextEnvment.getCallback().checkCancel();
                doExecute(classConfiguration,templatePlugin);
            }
        }catch (Exception e){
            e.printStackTrace();
            this.warnings.add(e.getMessage());
        }
    }
    /**
     * 执行生成
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/29 10:32
     * @param classConfiguration:类配置对象
     * @return: void
     */
    private void doExecute(ClassConfiguration classConfiguration,TemplatePlugin templatePlugin) throws FileNotFoundException, ClassNotFoundException {
        //获取目标对象
        Class targetClass = ObjectFactory.internalClassForName(classConfiguration.getClassName());
        this.contextEnvment.getCallback().startTask(" start generate test file:"+targetClass.getName());
        CompilationUnit compilationUnit = new CompilationUnit();
        ClassOrInterfaceDeclaration test = compilationUnit.addClass(targetClass.getName()+"Test");
        Map<String, Object> data = templatePlugin.getTemplateData(targetClass);
        List<String> importClasses  = (List<String>) data.get(TemplatePlugin.TEPMLATE_KEY_IMPORT_CLASSES);
        for(String classStr : importClasses){
            test.addImplementedType(classStr);
        }
        List<MethodMetadata> methodList = (List<MethodMetadata>) data.get(TemplatePlugin.TEPMLATE_KEY_METHODS);
        for(MethodMetadata methodMetadata : methodList){
            handleMethodMetadata(methodMetadata,test);
        }
        for(Method method : targetClass.getDeclaredMethods()){
            handleMethod(method,test);
        }
        this.contextEnvment.getCallback().startTask(" generate test file is done:"+compilationUnit.toString());


    }
    private void handleMethodMetadata(MethodMetadata method, ClassOrInterfaceDeclaration test) {
        //定义方法体
        BlockStmt blockStmt = new BlockStmt();
        test.addMethod(method.getMethodName()+"Test", Modifier.Keyword.PUBLIC).setBody(blockStmt);

        ExpressionStmt expressionStmt = new ExpressionStmt();
        expressionStmt.setExpression(method.getMethodBody());
        blockStmt.addStatement(expressionStmt);
    }
    /**
     * 处理方法
     * <li></li>
     * @author duanyong@jccfc.com
     * @date 2021/1/29 11:39
     * @param method: 当前方法
     * @param test:
     * @return: void
     */
    private void handleMethod(Method method, ClassOrInterfaceDeclaration test) {
        //定义方法体
        BlockStmt blockStmt = new BlockStmt();
        test.addMethod(method.getName()+"Test", Modifier.Keyword.PUBLIC).setBody(blockStmt);
        this.contextEnvment.getCallback().startTask("method.getName()->"+method.getTypeParameters());
        this.contextEnvment.getCallback().startTask("method.getName()->"+method.toGenericString());
        this.contextEnvment.getCallback().startTask("method.getReturnType()->"+method.getReturnType().getTypeName());
        this.contextEnvment.getCallback().startTask("method.getReturnType()+->"+method.getReturnType().getSimpleName());
        this.contextEnvment.getCallback().startTask("method.getParameterCount()+->"+method.getParameterCount());
        this.contextEnvment.getCallback().startTask("method.getGenericParameterTypes()+->"+method.getGenericParameterTypes());
        this.contextEnvment.getCallback().startTask("method.getGenericReturnType()+->"+method.getGenericReturnType());

        Type returnType = method.getGenericReturnType();
        if(returnType instanceof ParameterizedType){
            Type [] genericTypes =((ParameterizedType)returnType).getActualTypeArguments();
            for(Type genericType:genericTypes){
                this.contextEnvment.getCallback().startTask("返回值，泛型类型+->"+genericType);
                //doHandletype(returnType,blockStmt);
            }
        }
        //参数类型
        Type[] argTypes = MethodHelper.isGenericMode(method) ? method.getGenericParameterTypes() : method.getParameterTypes();



        doHandleType(method.getReturnType(),blockStmt);
        //定义方法返回体
        ReturnStmt returnStmt = new ReturnStmt();

        blockStmt.addStatement(returnStmt);

    }
    private void doHandleReturnType(Class<?> type,BlockStmt blockStmt) {
        System.out.println(" doHandletype-> " + type.getSimpleName());
        ExpressionStmt expressionStmt = new ExpressionStmt();
        VariableDeclarationExpr variableDeclarationExpr = new VariableDeclarationExpr();

        VariableDeclarator variableDeclarator = new VariableDeclarator();
        variableDeclarator.setName(StrKit.UnCapsFirst(type.getSimpleName()));
        variableDeclarator.setType(type.getSimpleName());
        variableDeclarator.setInitializer(getInitializer(type));

        NodeList<VariableDeclarator> variableDeclarators = new NodeList<>();
        variableDeclarators.add(variableDeclarator);
        variableDeclarationExpr.setVariables(variableDeclarators);

        expressionStmt.setExpression(variableDeclarationExpr);
        blockStmt.addStatement(expressionStmt);
    }
    private void doHandleType(Class<?> type,BlockStmt blockStmt) {
        System.out.println(" doHandletype-> " + type.getSimpleName());
        ExpressionStmt expressionStmt = new ExpressionStmt();
        VariableDeclarationExpr variableDeclarationExpr = new VariableDeclarationExpr();

        VariableDeclarator variableDeclarator = new VariableDeclarator();
        variableDeclarator.setName(StrKit.UnCapsFirst(type.getSimpleName()));
        variableDeclarator.setType(type.getSimpleName());
        variableDeclarator.setInitializer(getInitializer(type));

        NodeList<VariableDeclarator> variableDeclarators = new NodeList<>();
        variableDeclarators.add(variableDeclarator);
        variableDeclarationExpr.setVariables(variableDeclarators);

        expressionStmt.setExpression(variableDeclarationExpr);
        blockStmt.addStatement(expressionStmt);
    }

    public String getInitializer(Class<?> type){
        String className = type.getSimpleName();
        if (className.endsWith("List") || className.endsWith("Collection") || className.equals("AbstractList")) {
            return "new ArrayList<>()";
        } else if (className.endsWith("Set") || className.equals("AbstractSet")) {
            return "new HashSet<>()";
        } else if (className.endsWith("Queue") || className.equals("AbstractQueue")) {
            return "new LinkedList<>()";
        }else if (className.endsWith("Map") || className.equals("AbstractMap") || className.equals("HashMap")) {
            return "new HashMap<>()";
        } else if (className.equals("Dictionary") || className.endsWith("Hashtable")) {
            return "new Hashtable<>()";
        } else if ("String".equals(className)) {
            return "\"hello\"";
        }
        //对象类型
        return instanceType(type);
    }

    private String instanceType(Class<?> type) {
        Constructor constructor = null;
        if (!java.lang.reflect.Modifier.isAbstract(type.getModifiers())) {
            constructor = MethodHelper.chooseConstructor(type.getConstructors(), true);
        }
        if(constructor != null){
            return "new " + type.getSimpleName() + "(" + getInvokeParameters(constructor.getParameterTypes()) + ")";
        }

        return "new "+type.getSimpleName()+"()";
    }

    private String getInvokeParameters(Class[] paramTypes) {
        String params = "";
        for (int i = 0; i < paramTypes.length; i++) {
            if (paramTypes[i].getName().equals("byte")) {
                params += "(byte)" + PrimitiveType.get(paramTypes[i]) + ", ";

            } else if (paramTypes[i].getName().equals("short")) {
                params += "(short)" + PrimitiveType.get(paramTypes[i]) + ", ";
            } else {
                String s = PrimitiveType.get(paramTypes[i]);
                if (s.equals("null")) {
                    s = "(" + getClassName(paramTypes[i]) + ") " + s;
                }
                params += s + ", ";
            }
        }
        return params.contains(",") ? params.substring(0, params.lastIndexOf(",")) : params;
    }
    private String getClassName(Class clazz) {
        if (!clazz.getName().contains(".")) {
            return clazz.getTypeName();
        }
        String result = clazz.getTypeName();

        Class c = clazz;
        while (c.isArray()) {
            c = c.getComponentType();
        }

        Class outClass = c.getEnclosingClass();
        if (outClass != null) {
            String outClassName = outClass.getTypeName();
            return result.substring(0, outClassName.length()) + "." + result.substring(outClassName.length() + 1);
        }
        return result.substring(result.lastIndexOf(".") + 1);
    }
    private static void showReferenceTypeDeclaration(ResolvedReferenceTypeDeclaration resolvedReferenceTypeDeclaration,
        ProgressCallback callback){

        callback.startTask(String.format("== %s ==",
            resolvedReferenceTypeDeclaration.getQualifiedName()));
        callback.startTask(" fields:");
        resolvedReferenceTypeDeclaration.getAllFields().forEach(f ->
            callback.startTask(String.format("    %s %s", f.getType(), f.getName())));
        callback.startTask(" methods:");
        resolvedReferenceTypeDeclaration.getAllMethods().forEach(m ->
            callback.startTask(String.format("    %s", m.getQualifiedSignature())));
    }
}
